﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using Sitecore;
using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.Globalization;
using Sitecore.Shell.Framework;
using Sitecore.Visualization;
using Sitecore.Web.UI.HtmlControls;
using Sitecore.Web.UI.Pages;
using Sitecore.Web.UI.Sheer;
using Sitecore.Xml;

namespace FiveLimes.Sitecore.DeviceLayoutWizard
{
	public class AddDeviceLayoutWizardForm : WizardForm
	{
		private Item CurrentItem;
		private TemplateItem CurrentTemplate;
		private DeviceItem[] DeviceLayouts;
		private Dictionary<string,int> DeviceLIs;

		private Dictionary<string,string> Errors = new Dictionary<string, string>();

		protected Border SuccessMessage;
		protected Literal SuccessMessageText;
		protected Border ErrorMessage;
		protected Literal ErrorMessageText;
		protected Literal ReadyMessage;
		protected DataContext DataContext;
		protected DataTreeview Treeview;
		protected Combobox Devices;
		protected Checkbox Overwrite;
		protected Listview AffectedItems;
		protected Listview ReviewItems;
		protected TreePicker ItemRoot;
		protected Literal SelectNodesMessage;

		protected override bool ActivePageChanging( string page, ref string newpage )
		{
			if ( page == "SelectTemplate" && newpage == "SelectDevice" )
			{
				SetDeviceChoices();

				if ( CurrentItem == null )
				{ 
					Context.ClientPage.ClientResponse.Alert( "Please select a Template node" );
					Context.ClientPage.ClientResponse.Focus( this.Treeview.ID );
					return false;
				}

				if ( CurrentTemplate == null )
				{
					Context.ClientPage.ClientResponse.Alert( "Please select a Template node" );
					Context.ClientPage.ClientResponse.Focus( this.Treeview.ID );
					return false;
				}

				if ( CurrentTemplate.StandardValues == null )
				{
					Context.ClientPage.ClientResponse.Alert( "The selected Template does not have Standard Values defined" );
					Context.ClientPage.ClientResponse.Focus( this.Treeview.ID );
					return false;
				}

				if ( DeviceLayouts == null || DeviceLayouts.Length == 0 )
				{
					Context.ClientPage.ClientResponse.Alert( "Standard Values for the selected Template does not have a layout for any of its Devices defined" );
					Context.ClientPage.ClientResponse.Focus( this.Treeview.ID );
					return false;
				}
			}
			else if ( page == "Ready" && newpage == "LastPage" )
			{
				//set up a way to do this async... eventually
				//Context.ClientPage.SendMessage( this, "installer:startInstallation" );

				//get the source item
				DeviceItem diTarget = DeviceLayouts.Where( dl => dl.ID.ToString() == Devices.SelectedItem.Value ).First();

				LayoutField layout = new LayoutField( CurrentTemplate.StandardValues );
				XmlNode sourceLayout = layout.GetDeviceNode( diTarget );

				int countS = 0;
				
				foreach ( ListviewItem lvi in ReviewItems.Items )
				{
					if ( ApplyLayout( lvi.Value, sourceLayout, diTarget ) )
						countS++;
				}

				SuccessMessageText.Text = String.Format( SuccessMessageText.Text, diTarget.Name, countS );

				if ( Errors.Count > 0 )
				{
					Context.ClientPage.ClientResponse.SetStyle( "ErrorMessage", "display", "" );
					foreach ( string Id in Errors.Keys )
					{
						ErrorMessageText.Text += String.Format(
							"{0}Error with Item ID {1} at {2} : {3}",
							Environment.NewLine,
							Id,
							Context.ContentDatabase.GetItem( new ID( Id ) ).Paths.ContentPath,
							Errors[Id]
						);		
					}
				}
			}

			return true;
		}
		protected override void ActivePageChanged( string page, string oldPage )
		{
			base.ActivePageChanged( page, oldPage );

			base.NextButton.Header = "Next";

			if ( page == "SelectNodes" )
			{
				LoadAffectedNodes();
			}
			else if ( page == "Ready" )
			{
				DeviceItem diTarget = DeviceLayouts.Where( dl => dl.ID.ToString() == Devices.SelectedItem.Value ).First();
				ReadyMessage.Text = String.Format( ReadyMessage.Text, diTarget.Name );

				ShowActionList();
				base.NextButton.Header = "Apply";
			}
			else if ( page == "LastPage" )
			{
				base.BackButton.Disabled = true;
				base.NextButton.Disabled = true;
				base.CancelButton.Header = Translate.Text( "Close" );
			}
		}
		protected override void OnLoad( EventArgs e )
		{
			if ( !Context.ClientPage.IsEvent )
			{
				this.DataContext.GetFromQueryString();
				this.DataContext.Parameters = "databasename=" + Context.ContentDatabase.Name;

				this.ReviewItems.ColumnNames.Clear();
				this.ReviewItems.ColumnNames.Add( "Path", "Item Path" );
				this.ReviewItems.ColumnNames.Add( "Overwrite", "Overwrite?" );

				foreach ( DeviceItem displayDevice in Context.ContentDatabase.Resources.Devices.GetAll() )
					Devices.Controls.Add( new ListItem() { ID = Control.GetUniqueID( "ListItem" ), Header = displayDevice.Name, Value = displayDevice.ID.ToString() } );
			}
			else
			{
				SetContextNodes();
			}

			base.OnLoad( e );
		}

		[HandleMessage( "AddDeviceLayoutWizard:AffectedItemClick" )]
		protected void AffectedItemClick( Message msg )
		{
			Context.ClientPage.ClientResponse.DisableOutput();
			try
			{
				ListviewItem lvi = AffectedItems.Items.Where( itm => "chk_" + itm.ID == Context.ClientPage.ClientRequest.Source ).FirstOrDefault();
				if ( lvi != null )
					lvi.Checked = !lvi.Checked;
			}
			finally
			{
				Context.ClientPage.ClientResponse.EnableOutput();
			}
			Context.ClientPage.ClientResponse.Refresh( AffectedItems );
		}

		#region Helpers
		private void SetContextNodes()
		{
			CurrentItem = this.DataContext.GetFolder();
			if ( CurrentItem == null ) return;

			CurrentTemplate = new TemplateItem( CurrentItem );
			if ( CurrentTemplate == null || CurrentTemplate.StandardValues == null ) return;

			DeviceLIs = new Dictionary<string, int>();
			for ( int i=0; i < Devices.Controls.Count; i++ )
			{
				if ( Devices.Controls[i] is ListItem )
				{
					ListItem li = (ListItem)Devices.Controls[i];

					if ( !DeviceLIs.ContainsKey( li.Value ) )
						DeviceLIs.Add( li.Value, i );
				}
			}

			DeviceLayouts = GetDevicesWithLayout( CurrentTemplate );
		}
		private void SetDeviceChoices()
		{
			Context.ClientPage.ClientResponse.DisableOutput();
			try
			{
				if ( DeviceLayouts != null && DeviceLayouts.Length > 0 )
				{
					foreach ( ListItem li in Devices.Controls )
					{
						li.Disabled = true;
					}

					foreach ( var deviceLayout in DeviceLayouts )
					{
						if ( DeviceLIs.ContainsKey( deviceLayout.ID.ToString() ) )
							( (ListItem)Devices.Controls[DeviceLIs[deviceLayout.ID.ToString()]] ).Disabled = false;
					}
				}
			}
			finally
			{
				Context.ClientPage.ClientResponse.EnableOutput();
			}
			Context.ClientPage.ClientResponse.Refresh( Devices );
		}
		private DeviceItem[] GetDevicesWithLayout( TemplateItem ti )
		{
			LayoutField layout = new LayoutField( ti.StandardValues );

			if ( layout == null || String.IsNullOrEmpty( layout.Value ) )
				return new DeviceItem[] {};

			var devices = from d in Context.ContentDatabase.Resources.Devices.GetAll()
						  where layout.GetDeviceNode( d ) != null
						  select d;

			return devices.ToArray();
		}
		private void LoadAffectedNodes()
		{
			Context.ClientPage.ClientResponse.DisableOutput();
			AffectedItems.Controls.Clear();

			DeviceItem targetDevice = DeviceLayouts.Where( dl => dl.ID.ToString() == Devices.SelectedItem.Value ).FirstOrDefault();

			//get the items for this template
			try
			{
				string rootPath = Context.ContentDatabase.GetItem( new ID( ItemRoot.Value ) ).Paths.FullPath;

				global::Sitecore.Data.Query.Query qry = new global::Sitecore.Data.Query.Query( rootPath + "//descendant-or-self::*[@@templateid='" + CurrentTemplate.ID.ToString() + "']" );
				qry.Max = Int32.MaxValue;
				var ret = qry.Execute( new global::Sitecore.Data.Query.QueryContext( Context.ContentDatabase.DataManager ) );
				Item[] found = new Item[] {};

				if ( ret is global::Sitecore.Data.Query.QueryContext[] )
				{
					var items = (global::Sitecore.Data.Query.QueryContext[])ret;
					found = new Item[items.Length];
					for ( int i = 0; i < found.Length; i++ )
						found[i] = Context.ContentDatabase.Items[items[i].ID];
				}
				else if ( ret is global::Sitecore.Data.Query.QueryContext )
				{
					found = new Item[1] { Context.ContentDatabase.Items[((global::Sitecore.Data.Query.QueryContext)ret).ID] };
				}

				NextButton.Disabled = false;

				if ( found.Length == 0 )
				{
					ListviewItem lvi = new ListviewItem() {
						Header = "There are no items to display",
						Enabled = false,
						Checked = false
					};
	
					Context.ClientPage.AddControl( AffectedItems, lvi );
					NextButton.Disabled = true;
				}

				foreach ( var item in found )
				{
					ListviewItem lvi = new ListviewItem();
					lvi.Icon = item.Appearance.Icon;
					lvi.Header = item.Paths.ContentPath;
					lvi.ID = Control.GetUniqueID( "ListviewItem" );
					lvi.Value = item.ID.ToString();

					LayoutField layout = new LayoutField( item.Fields[FieldIDs.LayoutField] );
					if ( layout.GetDeviceNode( targetDevice ) == null )
					{
						lvi.Checked = true;
						lvi.ServerProperties.Add( "IsOverwrite", false );
					}
					else if ( layout.InnerField.ContainsStandardValue ) //if it's already the standard value it will get picked up automatically
					{
						lvi.Checked = false;
						lvi.Header += " [Standard value]";
						lvi.ServerProperties.Add( "IsOverwrite", false );
					}
					else
					{
						lvi.Checked = Overwrite.Checked;
						lvi.ServerProperties.Add( "IsOverwrite", true );
					}

					Context.ClientPage.AddControl( AffectedItems, lvi );
				}

				SelectNodesMessage.Text = String.Format( SelectNodesMessage.Text, found.Length );
			}
			finally
			{
				Context.ClientPage.ClientResponse.EnableOutput();
			}
			Context.ClientPage.ClientResponse.Refresh( AffectedItems );
			Context.ClientPage.ClientResponse.Refresh( SelectNodesMessage );
			Context.ClientPage.ClientResponse.Refresh( NextButton );
		}
		private void ShowActionList()
		{
			Context.ClientPage.ClientResponse.DisableOutput();
			try
			{
				ReviewItems.Controls.Clear();

				foreach ( ListviewItem item in AffectedItems.Items )
				{
					if ( item.Checked )
					{
						ListviewItem lvi = new ListviewItem();
						lvi.Icon = item.Icon;
						lvi.Header = item.Header;
						lvi.ColumnValues.Add( "Path", item.Header );
						lvi.ColumnValues.Add( "Overwrite", (bool)item.ServerProperties["IsOverwrite"] ? "Yes":"" );
						lvi.ID = Control.GetUniqueID( "ListviewItem" );
						lvi.Value = item.Value;
						Context.ClientPage.AddControl( ReviewItems, lvi );
					}
				}
			}
			finally
			{
				Context.ClientPage.ClientResponse.EnableOutput();
			}
			Context.ClientPage.ClientResponse.Refresh( ReviewItems );
		}
		private bool ApplyLayout( string targetItemId, XmlNode newLayout, DeviceItem targetDevice )
		{
			try
			{
				Item targetItem = Context.ContentDatabase.GetItem( new ID( targetItemId ) );
			    LayoutField field = targetItem.Fields[FieldIDs.LayoutField];
				XmlDocument data = field.Data;

				//if there is an existing device node, remove it; add in the new one in its place
				XmlNode newLayoutNode = data.ImportNode( newLayout, true );
				if ( data.DocumentElement != null )
				{
					XmlNode node2 = data.DocumentElement.SelectSingleNode("d[@id='" + targetDevice.ID.ToString()  + "']");
					if ( node2 != null ) XmlUtil.RemoveNode( node2 );
					node2 = newLayoutNode.CloneNode(true);
					XmlUtil.SetAttribute( "id", targetDevice.ID.ToString(), node2 );
					data.DocumentElement.AppendChild( node2 );
				}

				targetItem.Editing.BeginEdit();
				targetItem[FieldIDs.LayoutField] = data.OuterXml;
				targetItem.Editing.EndEdit();

				return true;
			}
			catch ( Exception ex )
			{
				if ( !Errors.ContainsKey( targetItemId ) )
					Errors.Add( targetItemId, ex.Message );
				else
					Errors[targetItemId] += Environment.NewLine + ex.Message;

				return false;
			}
		}
		#endregion
	}
}
